package com.gaogzhen.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gaogzhen.domain.Account;
import com.gaogzhen.domain.AccountDetail;
import com.gaogzhen.domain.Coin;
import com.gaogzhen.domain.Config;
import com.gaogzhen.dto.MarketDto;
import com.gaogzhen.feign.MarketServiceFeign;
import com.gaogzhen.mapper.AccountMapper;
import com.gaogzhen.mappers.AccountVoMapper;
import com.gaogzhen.model.RRException;
import com.gaogzhen.service.AccountDetailService;
import com.gaogzhen.service.AccountService;
import com.gaogzhen.service.CoinService;
import com.gaogzhen.service.ConfigService;
import com.gaogzhen.utils.SqlExecuteUtils;
import com.gaogzhen.vo.AccountVo;
import com.gaogzhen.vo.SymbolAssetVo;
import com.gaogzhen.vo.UserTotalAccountVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* @description ${description}
* @author Administrator
* @date 2022-11-03 11:43
* @version 1.0
*/
@Service
@Slf4j
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService{

    @Autowired
    private AccountDetailService accountDetailService;

    @Autowired
    private CoinService coinService;

    @Autowired
    private ConfigService configService;

    @Autowired
    private MarketServiceFeign marketServiceFeign;

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void transferAccountAmount(Long adminId, Long userId, Long coinId, BigDecimal num, BigDecimal fee, Long orderId, String remark, String businessType, Byte direction) {
        Account coinAccount = getCoinAccount(coinId, userId);
        if (coinAccount == null) {
            throw new RRException("用户当前该币种余额不存在");
        }
        BigDecimal balance = null;
        if (direction == 2) {
            balance = coinAccount.getBalanceAmount().subtract(num);
            if (balance.compareTo(BigDecimal.ZERO) < 0) {
                throw new RRException("账户余额不足，提现失败");
            }
        }
        // 增加一条流水记录
        AccountDetail accountDetail = new AccountDetail();
        accountDetail.setCoinId(coinId);
        accountDetail.setUserId(userId);
        accountDetail.setAmount(num);
        accountDetail.setFee(fee);
        accountDetail.setOrderId(orderId);
        accountDetail.setAccountId(coinAccount.getId());
        accountDetail.setRefAccountId(adminId);
        accountDetail.setRemark(remark);
        accountDetail.setBusinessType(businessType);
        accountDetail.setDirection(direction);

        SqlExecuteUtils.sqlExceptionHandler(a -> accountDetailService.save(a), accountDetail, "新增流水记录失败");
        if (direction == 1) {
            coinAccount.setBalanceAmount(coinAccount.getBalanceAmount().add(num));
        }else if (direction == 2) {
            coinAccount.setBalanceAmount(balance);
        }
        SqlExecuteUtils.sqlExceptionHandler(this::updateById, coinAccount, "添加流水记录失败");

    }

    @Override
    public Account getUserAccount(Long userId, String coinCode) {
        // 通过货币名称查询货币id
        Coin coin = coinService.findByCoinName(coinCode);
        if (coin == null) {
            throw new RRException("该用户无" + coinCode + "资产");
        }


        Account account = getOne(new LambdaQueryWrapper<Account>()
                .eq(Account::getUserId, userId)
                .eq(Account::getCoinId, coin.getId())
        );
        if (account == null) {
            throw new RRException("账户不存在");
        }
        // 设置买入和卖出比率
        Config buyRate = configService.getByCode("CNY2USDT");
        if (buyRate == null) {
            throw new RRException("买入比率未配置");
        }
        Config sellRate = configService.getByCode("USDT2CNY");
        if (sellRate == null) {
            throw new RRException("卖出比率未配置");
        }
        account.setBuyRate(new BigDecimal(buyRate.getValue()));
        account.setSellRate(new BigDecimal(sellRate.getValue()));
        return account;

    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void lockUserAmount(Account account, BigDecimal num, String type, Long orderId, BigDecimal fee) {
        BigDecimal balance = account.getBalanceAmount().subtract(num);
        account.setBalanceAmount(balance);
        account.setFreezeAmount(account.getFreezeAmount().add(num));
        SqlExecuteUtils.sqlExceptionHandler(this::updateById, account, "更新账户资金失败");
        AccountDetail accountDetail = new AccountDetail();
        accountDetail.setUserId(account.getUserId());
        accountDetail.setAccountId(account.getId());
        accountDetail.setCoinId(account.getCoinId());
        accountDetail.setDirection((byte) 2);
        accountDetail.setBusinessType(type);
        accountDetail.setAmount(num);
        accountDetail.setFee(fee);
        accountDetail.setOrderId(orderId);
        accountDetail.setRemark("提现");
        SqlExecuteUtils.sqlExceptionHandler(accountDetailService::save, accountDetail, "账户流水新增失败");
    }

    @Override
    public UserTotalAccountVo total(Long userId) {
        UserTotalAccountVo userTotalAccountVo = new UserTotalAccountVo();

        BigDecimal basicCoin = BigDecimal.ZERO;
        BigDecimal basicCoin2CNYRate = BigDecimal.ONE;
        List<AccountVo> accountVos = new ArrayList<>();
        // 获取用户账号
        List<Account> accounts = list(new LambdaQueryWrapper<Account>()
                                        .eq(Account::getUserId, userId)
        );
        if (CollectionUtils.isEmpty(accounts)) {
            userTotalAccountVo.setAssertList(accountVos);
            userTotalAccountVo.setAmountUs(basicCoin);
            userTotalAccountVo.setAmount(basicCoin);

            return userTotalAccountVo;
        }
        // 计算总资产
        AccountVoMapper accountVoMapper = AccountVoMapper.INSTANCE;

        // 查询币种信息
        List<Long> coinIds = accounts.stream().map(Account::getCoinId).collect(Collectors.toList());
        List<Coin> coins = coinService.listByIds(coinIds);
        if (CollectionUtils.isEmpty(coins)) {
            throw new RRException("币种信息有误");
        }
        Map<Long, Coin> coinMap = coins.stream().collect(Collectors.toMap(Coin::getId, Function.identity()));
        // 查询平台基础货币id
        Config basicCoinConfig = configService.getByCode("PLATFORM_COIN_ID");
        Long basicCoinId = Long.valueOf(basicCoinConfig.getValue());
        for (Account account : accounts) {
            AccountVo accountVo = accountVoMapper.convert2Dto(account);
            Coin coin = coinMap.get(accountVo.getCoinId());
            if (coin == null || coin.getStatus() != 1) {
                continue;
            }
            accountVo.setCoinName(coin.getName());
            accountVo.setCoinType(coin.getType());
            accountVo.setCoinImgUrl(coin.getImg());
            accountVo.setWithdrawFlag(coin.getWithdrawFlag());
            accountVo.setRechargeFlag(coin.getRechargeFlag());
            accountVo.setFeeRate(BigDecimal.valueOf(coin.getRate()));
            accountVo.setMinFeeNum(coin.getMinFeeNum());
            accountVos.add(accountVo);
            // 计算总的账面余额
            BigDecimal volume = accountVo.getBalanceAmount().add(accountVo.getFreezeAmount());
            accountVo.setCarryingAmount(volume);
            Long coinId = accountVo.getCoinId();
            BigDecimal basicCoinAmount = coinId.equals(basicCoinId) ? volume: convert2BasicCoin(coin.getName(), volume, basicCoinId, coinMap);
            basicCoin = basicCoin.add(basicCoinAmount);
        }


        userTotalAccountVo.setAmount(basicCoin.multiply(basicCoin2CNYRate).setScale(8, RoundingMode.HALF_UP));
        userTotalAccountVo.setAmountUs(basicCoin);
        userTotalAccountVo.setAssertList(accountVos);
        return userTotalAccountVo;
    }

    @Override
    public SymbolAssetVo getSymbolAsset(Long userId, String symbol) {
        SymbolAssetVo symbolAssetVo = new SymbolAssetVo();
        MarketDto marketDto = marketServiceFeign.findBySymbol(symbol);
        Long sellCoinId = marketDto.getSellCoinId();
        Long buyCoinId = marketDto.getBuyCoinId();
        List<Account> accounts = list(new LambdaQueryWrapper<Account>()
                .eq(Account::getUserId, userId)
                .in(Account::getCoinId, Arrays.asList(sellCoinId, buyCoinId))
        );
//        List<Account> accounts = listByIds(Arrays.asList(sellCoinId, buyCoinId));
        Account account = accounts.get(0);
        Account buy = null;
        Account sell = null;
        if (account.getId().equals(buyCoinId)) {
            buy = account;
            sell = accounts.get(1);
        }  else {
            buy = accounts.get(1);
            sell = account;
        }
        String[] names = marketDto.getName().split("/");
        symbolAssetVo.setBuyAmount(buy.getBalanceAmount());
        symbolAssetVo.setBuyFeeRate(marketDto.getFeeBuy());
        symbolAssetVo.setBuyLockAmount(buy.getFreezeAmount());
        symbolAssetVo.setBuyUnit(names[1]);

        symbolAssetVo.setSellAmount(sell.getBalanceAmount());
        symbolAssetVo.setSellFeeRate(marketDto.getFeeSell());
        symbolAssetVo.setSellLockAmount(sell.getFreezeAmount());
        symbolAssetVo.setSellUnit(names[0]);
        return symbolAssetVo;
    }

    /**
     * 当前币种金额转换为基础货币金额
     * @param coinName    当前货币名称
     * @param volume    当前货币金额
     * @param basicCoinId   基础货币id
     * @param coinMap   货币map
     * @return          基础货币金额
     */
    private BigDecimal convert2BasicCoin(String coinName, BigDecimal volume, Long basicCoinId, Map<Long, Coin> coinMap) {
        Coin coin = coinMap.get(basicCoinId);
        String symbol = coinName + "/" + coin.getName();
        MarketDto marketDto = marketServiceFeign.findBySymbol(symbol);

        return marketDto != null? marketDto.getOpenPrice(): BigDecimal.ZERO;
    }

    private Account getCoinAccount(Long coinId, Long userId) {
        return getOne(new LambdaQueryWrapper<Account>()
                        .eq(Account::getCoinId, coinId)
                        .eq(Account::getUserId, userId)
                        .eq(Account::getStatus, 1)
        );
    }

    /**
     * 暂时锁定用户的资产
     *
     * @param userId  用户的id
     * @param coinId  币种的id
     * @param mum     锁定的金额
     * @param type    资金流水的类型
     * @param orderId 订单的Id
     * @param fee
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void lockUserAmount(Long userId, Long coinId, BigDecimal mum, String type, Long orderId, BigDecimal fee) {
        Account account = getOne(new LambdaQueryWrapper<Account>().eq(Account::getUserId, userId)
                .eq(Account::getCoinId, coinId)
        );
        if (account == null) {
            throw new IllegalArgumentException("您输入的资产类型不存在");
        }
        BigDecimal balanceAmount = account.getBalanceAmount();
        if (balanceAmount.compareTo(mum) < 0) { // 库存的操作
            throw new IllegalArgumentException("账号的资金不足");
        }
        account.setBalanceAmount(balanceAmount.subtract(mum));
        account.setFreezeAmount(account.getFreezeAmount().add(mum));
//        boolean updateById = updateById(account);
        SqlExecuteUtils.sqlExceptionHandler(this::updateById, account, "更新账户失败");
        // 增加流水记录
        AccountDetail accountDetail = new AccountDetail(
                null,
                userId,
                coinId,
                account.getId(),
                account.getId(), // 如果该订单时邀请奖励,有我们的ref的account ,否则,值和account 是一样的
                orderId,
                (byte) 2,
                type,
                mum,
                fee,
                "用户提现",
                null,
                null,
                null
        );
        SqlExecuteUtils.sqlExceptionHandler(a -> accountDetailService.save(a), accountDetail, "新增流水记录失败");
    }

    @Override
    public void transferBuyAmount(Long fromUserId, Long toUserId, Long coinId, BigDecimal amount, String businessType, Long orderId) {
        Account fromAccount = getCoinAccount(coinId, fromUserId);
        if (fromAccount == null) {
            log.error("资金划转-资金账户异常，userId:{}, coinId:{}", fromUserId, coinId);
            throw new IllegalArgumentException("资金账户异常");
        } else {
            Account toAccount = getCoinAccount(coinId, toUserId);
            if (toAccount == null) {
                throw new IllegalArgumentException("资金账户异常");
            } else {
                boolean count1 = decreaseAmount(fromAccount, amount);
                boolean count2 = addAmount(toAccount, amount);
                if (count1 && count2) {
                    List<AccountDetail> accountDetails = new ArrayList(2);
                    AccountDetail fromAccountDetail = new AccountDetail(fromUserId, coinId, fromAccount.getId(), toAccount.getId(), orderId, 2, businessType, amount, BigDecimal.ZERO, businessType);
                    AccountDetail toAccountDetail = new AccountDetail(toUserId, coinId, toAccount.getId(), fromAccount.getId(), orderId, 1, businessType, amount, BigDecimal.ZERO, businessType);
                    accountDetails.add(fromAccountDetail);
                    accountDetails.add(toAccountDetail);

//                    accountDetails.addAll(accountDetails);
                } else {
                    throw new RuntimeException("资金划转失败");
                }
            }
        }
    }

    private boolean addAmount(Account account, BigDecimal amount) {
        account.setBalanceAmount(account.getBalanceAmount().add(amount));
        return updateById(account);
    }

    private boolean decreaseAmount(Account account, BigDecimal amount) {
        account.setBalanceAmount(account.getBalanceAmount().subtract(amount));
        return updateById(account);
    }

    @Override
    public void transferSellAmount(Long fromUserId, Long toUserId, Long coinId, BigDecimal amount, String businessType, Long orderId) {
        Account fromAccount = getCoinAccount(coinId, fromUserId);
        if (fromAccount == null) {
            log.error("资金划转-资金账户异常，userId:{}, coinId:{}", fromUserId, coinId);
            throw new IllegalArgumentException("资金账户异常");
        } else {
            Account toAccount = getCoinAccount(coinId, toUserId);
            if (toAccount == null) {
                throw new IllegalArgumentException("资金账户异常");
            } else {
                boolean count1 = addAmount(fromAccount, amount);
                boolean count2 = decreaseAmount(toAccount, amount);
                if (count1 && count2) {
                    List<AccountDetail> accountDetails = new ArrayList(2);
                    AccountDetail fromAccountDetail = new AccountDetail(fromUserId, coinId, fromAccount.getId(), toAccount.getId(), orderId, 2, businessType, amount, BigDecimal.ZERO, businessType);
                    AccountDetail toAccountDetail = new AccountDetail(toUserId, coinId, toAccount.getId(), fromAccount.getId(), orderId, 1, businessType, amount, BigDecimal.ZERO, businessType);
                    accountDetails.add(fromAccountDetail);
                    accountDetails.add(toAccountDetail);

//                    accountDetails.addAll(accountDetails);
                } else {
                    throw new RuntimeException("资金划转失败");
                }
            }
        }
    }
}
